package com.gee.spring.cloud.feign.flow.handler;

import com.gee.spring.cloud.feign.flow.exception.GlobalSkippedException;
import com.gee.spring.cloud.feign.flow.exception.SkippedException;
import com.gee.spring.cloud.feign.flow.execute.ExecuteContext;
import com.gee.spring.cloud.feign.flow.execute.WorkExecuteProperties;
import com.gee.spring.cloud.feign.flow.execute.nextwork.Condition;
import com.gee.spring.cloud.feign.flow.execute.nextwork.NextWorkWrapper;
import com.gee.spring.cloud.feign.flow.result.CheckResult;
import com.gee.spring.cloud.feign.flow.result.ExecuteState;
import com.gee.spring.cloud.feign.flow.result.WorkResult;
import com.gee.spring.cloud.feign.flow.work.AbstractWork;
import com.gee.spring.cloud.feign.flow.work.impl.EndWork;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * desc:
 *
 * @author gee wrote on  2021-01-15 13:42:13
 */
public abstract class AbstractWorkHandler implements WorkHandler {

    /**
     *  handle before the work executes
     * @param currentWork the current work
     * @param executeContext the execute context
     */
    @Override
    public void handleBeforeBegin(AbstractWork<?> currentWork, ExecuteContext executeContext){
        /*
         * after the current has been WORKING, try to set the previous works's execute state to be EXCEPTIONAL
         * to void execute
         */
        WorkExecuteProperties< ?> workExecuteProperties = currentWork.getWorkExecuteProperties();
        SkippedException skippedException = new SkippedException(FOLLOW_UP_HAS_BEGUN + currentWork.getId());
        if (workExecuteProperties.getPreWorks().size() > 0) {
            workExecuteProperties.getPreWorks().forEach((preId, preWork) -> {
                trySkipPrevious(preWork, skippedException,executeContext);
            });
        }
        WorkResult< ?> workResult = executeContext.getWorkResult(currentWork.getId());
        postBeforeBegin(currentWork.getId(), workResult);
    }


    @Override
    @SuppressWarnings("unchecked")
    public void handleAfterFinish(AbstractWork<?> currentWork, ExecuteContext executeContext){
        String currentWorkId = currentWork.getId();
        WorkResult workResult = executeContext.getWorkResult(currentWorkId);
        AtomicInteger executeState = workResult.getExecuteState();
        Exception exception = workResult.getException();
        if (exception instanceof GlobalSkippedException){
            AtomicInteger globalExecuteState = executeContext.getGlobalExecuteState();
            if (globalExecuteState.compareAndSet(ExecuteState.WORKING.getCode(), ExecuteState.EXCEPTIONAL.getCode())) {
                String msg = WorkHandler.WHOLE_SKIPPED_CAUSED_BY + currentWorkId;
                SkippedException skippedException = new SkippedException(msg);
                executeContext.setGlobalException(skippedException);
                executeContext.setExecuteMessage(msg);
            }
        }
        if (executeContext.getGlobalExecuteState().get() == ExecuteState.EXCEPTIONAL.getCode()
            && (executeState.compareAndSet(ExecuteState.WORKING.getCode(), ExecuteState.EXCEPTIONAL.getCode())
                || executeState.get() == ExecuteState.EXCEPTIONAL.getCode())){
            beginNext(currentWork, executeState, executeContext);
        }else {
            ExecuteState currentWorkExecuteState;
            if (exception == null) {
                currentWorkExecuteState = ExecuteState.SUCCESSFUL;
            }else {
                currentWorkExecuteState = ExecuteState.EXCEPTIONAL;
            }
            postBeforeNextBegin(currentWork, currentWorkExecuteState, executeContext);
            editNextWork(currentWork, currentWorkExecuteState, executeContext);
            executeState.compareAndSet(ExecuteState.WORKING.getCode(), currentWorkExecuteState.getCode());
            // after the the current work finished, let the next works decide to execute or direct to be exceptional
            beginNext(currentWork, executeState, executeContext);
        }
        postAfterNextBegin(currentWork ,executeContext);
    }

    private void beginNext(AbstractWork< ?> currentWork, AtomicInteger executeState, ExecuteContext executeContext) {
        String currentWorkId = currentWork.getId();
        WorkResult workResult = executeContext.getWorkResult(currentWorkId);
        WorkExecuteProperties<?> workExecuteProperties = currentWork.getWorkExecuteProperties();
        if (workExecuteProperties.getNextWorks().size() > 0){
            workExecuteProperties.getNextWorks().forEach((nextWorkId, nextWorkWrapper) -> {
                AbstractWork<  ?> nextWork = nextWorkWrapper.getWork();
                WorkExecuteProperties< ?> nextWorkExecuteProperties = nextWork.getWorkExecuteProperties();
                AtomicReference<String> preFUSWorkId = nextWorkExecuteProperties.getPreFUSWorkId();
                Condition condition = nextWorkWrapper.getCondition();
                if (!nextWorkWrapper.getNecessaryForNext()
                        && condition.determine(executeState.get(),workResult.getResult())
                        && preFUSWorkId.get() == null){
                    preFUSWorkId.compareAndSet(null, currentWorkId);
                }
                CheckResult checkResult = nextWork.getWorkHandler().check(nextWork, executeContext);
                if (checkResult.getExpectState() != ExecuteState.INIT){
                    if (checkResult.getExpectState() == ExecuteState.WORKING) {
                        AtomicReference<String> preWorkId = nextWorkExecuteProperties.getPreWorkId();
                        preWorkId.compareAndSet(null, currentWorkId);
                    }
                    nextWorkExecuteProperties.setCheckResult(checkResult);
                    CompletableFuture<ExecuteContext> completableFuture = workExecuteProperties.getCompletableFuture()
                            .whenCompleteAsync(nextWork, executeContext.getExecutor());
                    nextWork.getWorkExecuteProperties().setCompletableFuture(completableFuture);
                }
            });
        }
    }

    /**
     *  在单元结束后, 可以动态的修改后置单元
     * @param currentWork 当前单元
     * @param executeContext  整体的执行上下文
     */
    public void editNextWork(AbstractWork<?> currentWork, ExecuteState currentWorkExecuteState,
                                ExecuteContext executeContext){

    }


    @Override
    public CheckResult check(AbstractWork<?> currentWork, ExecuteContext executeContext) {
        String currentWorkId = currentWork.getId();
        AtomicInteger currentExecuteState = executeContext.getWorkResultMap().get(currentWorkId).getExecuteState();
        if (executeContext.getGlobalExecuteState().get() == ExecuteState.EXCEPTIONAL.getCode() &&
                updExecuteStateInit2Working(currentExecuteState)){
            Exception skippedException = executeContext.getGlobalException();
            return CheckResult.build(ExecuteState.EXCEPTIONAL, skippedException);
        }
        if (currentExecuteState.get() != ExecuteState.INIT.getCode()){
            return CheckResult.build(ExecuteState.INIT);
        }
        Map<String, AbstractWork<?>> preWorks = currentWork.getWorkExecuteProperties().getPreWorks();
        return checkPreWorks(currentWorkId, currentExecuteState, preWorks, executeContext);
    }

    /**
     *  根据前一级的单元执行情况, 判断自身是否应该执行
     *  除DefaultWorkHandler的判断方式外, 可能的需求: 前一级单元都执行失败, 本单元才执行
     * @return  CheckResult
     */
    protected abstract CheckResult checkPreWorks(String currentWorkId, AtomicInteger currentExecuteState,
                                                 Map<String, AbstractWork<  ?>> preWorks,
                                                 ExecuteContext executeContext);

    protected abstract void postBeforeBegin(String currentWorkId, Object param);

    /**
     *  post after the current work finished and before the next works begin
     *  pay attention to the current work's execute state is still WORKING saved in the executeContext
     * @param currentWork the current work's id
     * @param currentWorkExecuteState final execute state of the current work
     * @param executeContext  the execute context
     */
    public abstract void postBeforeNextBegin(AbstractWork<?> currentWork,
                                                           ExecuteState currentWorkExecuteState,
                                                           ExecuteContext executeContext);

    /**
     *  post after the current work finished and after the next works begin
     * @param currentWork the current work's id
     * @param executeContext the execute context
     */
    public abstract void postAfterNextBegin(AbstractWork<?> currentWork,
                                                          ExecuteContext executeContext);

    /**
     *  update the executeState from INIT to WORKING
     * @param executeState the execute state
     * @return return true if update successfully, otherwise false
     */
    protected final boolean updExecuteStateInit2Working(AtomicInteger executeState) {
        return executeState.compareAndSet(ExecuteState.INIT.getCode(), ExecuteState.WORKING.getCode());
    }

    protected AbstractWork<  ?> getEndWork(AbstractWork<  ?> currentWork){
        if (currentWork == null){
            return null;
        }
        Map<String, ? extends NextWorkWrapper< ?>> nextWorkWrappers = currentWork.getWorkExecuteProperties().getNextWorks();
        if (nextWorkWrappers.containsKey(EndWork.END_WORK_ID)){
            return nextWorkWrappers.get(EndWork.END_WORK_ID).getWork();
        }
        Optional<? extends NextWorkWrapper< ?>> oneNextWorkOpt = nextWorkWrappers.values().stream().findAny();
        if (oneNextWorkOpt.isPresent()){
            NextWorkWrapper< ?> nextWorkWrapper = oneNextWorkOpt.get();
            return getEndWork(nextWorkWrapper.getWork());
        }
        return null;
    }

    /**
     *  get all unnecessary previous works's id of the current work
     * @param currentWorkId  the current work's id
     * @param preWorks the current work's  previous works
     * @return  all unnecessary previous works's id of the current work
     */
    protected final String getAllPreUnnecessaryIds(String currentWorkId, Map<String, AbstractWork<  ?>> preWorks) {
        List<String> list = new ArrayList<>();
        preWorks.forEach((key, work) -> {
            if (!work.getWorkExecuteProperties().getNextWorks().get(currentWorkId).getNecessaryForNext()){
                list.add(work.getId());
            }
        });
        return list.toString();
    }

    /**
     *  get One of the previous necessary works which execute exceptionally of the current work
     * @param currentWorkId the current work's id
     * @param preWorks the current work's  previous works
     * @param executeContext the execute context
     * @return One of the previous necessary works which execute exceptionally of the current work
     */
    @SuppressWarnings("unchecked")
    protected final AbstractWork<  ?> oneOfNecessaryException(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext) {
        return preWorks.values().parallelStream().filter(
                preWork -> {
                    NextWorkWrapper nextWorkWrapper = preWork.getWorkExecuteProperties().getNextWorks().get(currentWorkId);
                    WorkResult preWorkResult = executeContext.getWorkResult(preWork.getId());
                    return nextWorkWrapper.getNecessaryForNext()
                            && preWorkResult.getExecuteState().get() > ExecuteState.WORKING.getCode()
                            && ! nextWorkWrapper.getCondition()
                            .determine(preWorkResult.getExecuteState().get(),preWorkResult.getResult());
                    }
                ).findAny().orElse(null);
    }

    /**
     *  any of the previous necessary works of the current work is still INIT or WORKING
     * @param currentWorkId the current work's id
     * @param preWorks the current work's  previous works
     * @param executeContext the execute context
     * @return true if any of the previous necessary works of the current work is still INIT or WORKING,
     *          otherwise false
     */
    protected final boolean anyNecessaryUnfinished(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext) {
        return preWorks.values().parallelStream().anyMatch(work -> {
                    Map<String, ? extends NextWorkWrapper< ?>> nextWorkWrapperMap = work.getWorkExecuteProperties().getNextWorks();
                    return nextWorkWrapperMap.get(currentWorkId).getNecessaryForNext()
                            && executeContext.getWorkResult(work.getId()).getExecuteState().get()
                            < ExecuteState.SUCCESSFUL.getCode();
                }
        );
    }

    /**
     *  none of the previous unnecessary works of the current work  executes successfully
     * @param currentWorkId the current work's id
     * @param preWorks the current work's  previous works
     * @param executeContext the execute context
     * @return true if none of the previous unnecessary works of the current work  executes successfully,
     *          otherwise false
     */
    @SuppressWarnings("unchecked")
    protected final boolean noneUnnecessarySuccess(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext) {
        return preWorks.values().parallelStream().noneMatch(work -> {
            Map<String, ? extends NextWorkWrapper< ?>> nextWorkWrapperMap = work.getWorkExecuteProperties().getNextWorks();
            Condition condition = nextWorkWrapperMap.get(currentWorkId).getCondition();
            WorkResult< ?> workResult = executeContext.getWorkResult(work.getId());
            return ! nextWorkWrapperMap.get(currentWorkId).getNecessaryForNext()
                    && condition.determine(workResult.getExecuteState().get(),workResult.getResult());
        });
    }

    /**
     *  any of the previous unnecessary works of the current work is still INIT or WORKING
     * @param currentWorkId  the current work's id
     * @param preWorks the current work's  previous works
     * @param executeContext the execute context
     * @return true if any of the previous unnecessary works of the current work is still INIT or WORKING,
     *          otherwise false
     */
    protected final boolean anyUnnecessaryUnfinished(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext) {
        return preWorks.values().parallelStream().anyMatch(work ->{
            Map<String, ? extends NextWorkWrapper< ?>> nextWorkWrapperMap = work.getWorkExecuteProperties().getNextWorks();
            return !nextWorkWrapperMap.get(currentWorkId).getNecessaryForNext()
                    && executeContext.getWorkResult(work.getId()).getExecuteState().get()
                    < ExecuteState.SUCCESSFUL.getCode();
        });
    }

    /**
     *  the previous unnecessary works of the current work exists and have been exceptional
     * @param currentWorkId the current work's id
     * @param preWorks the current work's  previous works
     * @param executeContext the execute context
     * @return true if the previous unnecessary works of the current work exists and have been exceptional
     *          otherwise false
     */
    @SuppressWarnings("unchecked")
    protected final boolean allUnnecessaryException(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext) {

        long unnecessary = preWorks.values().parallelStream()
                .filter(work -> !work.getWorkExecuteProperties().getNextWorks().get(currentWorkId).getNecessaryForNext()).count();
        long unnecessaryExceptionCount = preWorks.values().parallelStream()
                .filter(work -> {
                    NextWorkWrapper nextWorkWrapper = work.getWorkExecuteProperties()
                            .getNextWorks().get(currentWorkId);
                    WorkResult< ?> workResult = executeContext.getWorkResult(work.getId());
                    return !nextWorkWrapper.getNecessaryForNext()
                            && workResult.getExecuteState().get() > ExecuteState.WORKING.getCode()
                            && !nextWorkWrapper.getCondition().determine(workResult.getExecuteState().get(), workResult.getResult());

                }).count();
        return unnecessary > 0 && unnecessary == unnecessaryExceptionCount;
    }

    /**
     *  the previous unnecessary works of the current work exists
     * @param currentWorkId the current work's id
     * @param preWorks the current work's  previous works
     * @return true if the previous unnecessary works of the current work exists
     *          otherwise false
     */
    protected final boolean existUnnecessary(String currentWorkId
            , Map<String, AbstractWork<  ?>> preWorks, ExecuteContext executeContext){
        return preWorks.values().parallelStream()
                .anyMatch(work -> !work.getWorkExecuteProperties().getNextWorks().get(currentWorkId).getNecessaryForNext());
    }

    /**
     *  any of the previous works of the current work is still INIT or WORKING
     * @param preWorks the previous works of the current work
     * @param executeContext the execute context
     * @return true if any of the previous works of the current work is still INIT or WORKING
     *          otherwise false
     */
    protected boolean anyPreUnfinished(Map<String, AbstractWork<?>> preWorks, ExecuteContext executeContext){
        return preWorks.values().stream().anyMatch(preWork ->
                executeContext.getWorkResult(preWork.getId()).getExecuteState().get() < 2
        );
    }
}
